package com.gitee.wsl.net.ssdp

import com.gitee.wsl.net.ssdp.AddNotification.Companion.validateNotification
import com.gitee.wsl.net.ssdp.description.DescriptionService
import com.gitee.wsl.net.ssdp.description.DescriptionService.createDeviceManifest
import com.gitee.wsl.net.ssdp.description.model.DeviceManifest
import com.gitee.wsl.net.ssdp.description.model.DeviceManifest.*
import com.gitee.wsl.net.ssdp.description.model.DeviceManifest.Service.*
import com.gitee.wsl.net.ssdp.description.model.DeviceManifest.Service.Action.*
import com.gitee.wsl.net.ssdp.description.model.xml.SpecVersion
import com.gitee.wsl.net.ssdp.message.SsdpMessage
import com.gitee.wsl.net.ssdp.message.Headers
import io.ktor.client.engine.mock.*
import io.ktor.http.*
import io.ktor.util.date.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
import org.mockito.Mockito.*
//import pro.leandev.zeroconf.description.DescriptionService
//import pro.leandev.zeroconf.description.DescriptionService.createDeviceManifest
//import pro.leandev.zeroconf.description.model.DeviceManifest
//import pro.leandev.zeroconf.description.model.DeviceManifest.Device
//import pro.leandev.zeroconf.description.model.DeviceManifest.Icon
//import pro.leandev.zeroconf.description.model.DeviceManifest.Service
//import pro.leandev.zeroconf.description.model.DeviceManifest.Service.Action
//import pro.leandev.zeroconf.description.model.DeviceManifest.Service.Action.Argument
//import pro.leandev.zeroconf.description.model.DeviceManifest.Service.StateVariable
//import pro.leandev.zeroconf.description.model.xml.SpecVersion
//import pro.leandev.zeroconf.discovery.AddNotification.Companion.validateNotification
//import pro.leandev.zeroconf.discovery.message.SsdpMessage
import java.lang.Thread.sleep
import java.net.DatagramPacket
import java.net.InetAddress
import java.net.NetworkInterface
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals

class ControlPointTest {

  //  @Test
//    fun test() {
//        val cp = ControlPoint("testCP")
//        cp.start()
//        cp.search()
//        sleep(60000)
//        cp.stop()
//    }

    private lateinit var notificationService: NotificationService
    private lateinit var testScope: TestScope
    private lateinit var messagesChannel: Channel<SsdpMessage>

    @BeforeTest
    fun setUp() {
        // Mock the NetworkInterface and INotifyServerPool
        val networkInterface = mock(NetworkInterface::class.java).apply {
            `when`(displayName).thenReturn("eth0")
        }

        // Use TestCoroutineDispatcher for controlled coroutine execution
        testScope = TestScope()

        messagesChannel = Channel<SsdpMessage>(10)
        val notifyServerPool = spy(object : INotifyServerPool {
            override val messages: ReceiveChannel<SsdpMessage> = messagesChannel
            override fun start(interfaceNames: List<String>?) {}
            override fun stop(interfaceNames: List<String>?) {}
            override fun search(target: String?, inetAddr: InetAddress?) {}
        })

        // Instantiate the NotificationService with the mocked NotifyServerPool
        notificationService = NotificationService(
            "testCPName",
            listOf(networkInterface),
            pool = notifyServerPool
        )
        notificationService.start()
    }

    private fun getHeaders(contentSize: Int) = headersOf(
        HttpHeaders.AcceptRanges to listOf("bytes"),
        HttpHeaders.Connection to listOf("close"),
        HttpHeaders.ContentLength to listOf(contentSize.toString()),
        HttpHeaders.ContentType to listOf("text/xml"),
        HttpHeaders.Date to listOf(GMTDate(System.currentTimeMillis()-100).toString()),
        HttpHeaders.LastModified to listOf("Thu, 21 Nov 2024 04:31:39 GMT"),
        HttpHeaders.Server to listOf("Linux/4.4.302+, UPnP/1.0, Portable SDK for UPnP devices/1.12.1"),
        "X-User-Agent" to listOf("redsonic")
    )

    val expectedDeviceManifest = DeviceManifest(
        usn = "uuid:9009d02e-6888-9999-7861-78612ed00990::urn:schemas-upnp-org:device:MediaServer:1",
        metadata = DeviceManifest.Metadata(
            location = Url("http://192.0.0.33:50001/desc/device.xml"),
            urns = mapOf("" to "urn:schemas-upnp-org:device-1-0"),
            specVersion = SpecVersion(major = 1, minor = 0),
        ),
        rootDevice = Device(
            deviceType = "urn:schemas-upnp-org:device:MediaServer:1",
            friendlyName = "Test_NAS",
            manufacturer = "Synology Inc",
            manufacturerUrl = Url("http://www.synology.com/"),
            modelDescription = "Synology DLNA/UPnP Media Server",
            modelName = "DS220+",
            modelNumber = null,
            modelUrl = Url("http://www.synology.com/"),
            serialNumber = "2280XXXXXXXXXXX",
            udn = "uuid:9009d02e-6888-9999-7861-78612ed00990",
            originUdn = null,
            upc = null,
            presentationUrl = Url("http://192.0.0.33:50001/ContentDirectory/event"),
            icons = listOf(
                Icon(
                    mimetype = "image/jpeg",
                    width = 120,
                    height = 120,
                    depth = 24,
                    url = Url("http://192.0.0.33:50001/tmp_icon/dmsicon120.jpg")
                ),
                Icon(
                    mimetype = "image/jpeg",
                    width = 48,
                    height = 48,
                    depth = 24,
                    url = Url("http://192.0.0.33:50001/tmp_icon/dmsicon48.jpg")
                ),
                Icon(
                    mimetype = "image/png",
                    width = 120,
                    height = 120,
                    depth = 24,
                    url = Url("http://192.0.0.33:50001/tmp_icon/dmsicon120.png")
                ),
                Icon(
                    mimetype = "image/png",
                    width = 48,
                    height = 48,
                    depth = 24,
                    url = Url("http://192.0.0.33:50001/tmp_icon/dmsicon48.png")
                )
            ),
            services = listOf(
                Service(
                    metadata = DeviceManifest.Metadata(
                        location = Url("http://192.0.0.33:50001/connmgrSCPD.xml"),
                        urns = emptyMap(),
                        specVersion = SpecVersion(major = 1, minor = 0),
                    ),
                    serviceType = "urn:schemas-upnp-org:service:ConnectionManager:1",
                    serviceId = "urn:upnp-org:serviceId:ConnectionManager",
                    scpdUrl = Url("http://192.0.0.33:50001/connmgrSCPD.xml"),
                    controlUrl = Url("http://192.0.0.33:50001/ConnectionManager/control"),
                    eventSubUrl = Url("http://192.0.0.33:50001/ConnectionManager/event"),
                    actionList = listOf(
                        Action(
                            name = "GetCurrentConnectionIDs",
                            arguments = listOf(
                                Argument(
                                    name = "ConnectionIDs",
                                    direction = "out",
                                    relatedStateVariable = "CurrentConnectionIDs"
                                )
                            )
                        ),
                        Action(
                            name = "GetCurrentConnectionInfo",
                            arguments = listOf(
                                Argument(
                                    name = "ConnectionID",
                                    direction = "in",
                                    relatedStateVariable = "A_ARG_TYPE_ConnectionID"
                                ),
                                Argument(name = "RcsID", direction = "out", relatedStateVariable = "A_ARG_TYPE_RcsID"),
                                Argument(
                                    name = "AVTransportID",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_AVTransportID"
                                ),
                                Argument(
                                    name = "ProtocolInfo",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_ProtocolInfo"
                                ),
                                Argument(
                                    name = "PeerConnectionManager",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_ConnectionManager"
                                ),
                                Argument(
                                    name = "PeerConnectionID",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_ConnectionID"
                                ),
                                Argument(
                                    name = "Direction",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_Direction"
                                ),
                                Argument(
                                    name = "Status",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_ConnectionStatus"
                                )
                            )
                        ),
                        Action(
                            name = "GetProtocolInfo",
                            arguments = listOf(
                                Argument(
                                    name = "Source",
                                    direction = "out",
                                    relatedStateVariable = "SourceProtocolInfo"
                                ),
                                Argument(name = "Sink", direction = "out", relatedStateVariable = "SinkProtocolInfo")
                            )
                        )
                    ),
                    serviceStateTable = listOf(
                        StateVariable(
                            name = "A_ARG_TYPE_ProtocolInfo",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_ConnectionStatus",
                            dataType = "string",
                            allowedValueList = listOf(
                                "OK",
                                "ContentFormatMismatch",
                                "InsufficientBandwidth",
                                "UnreliableChannel",
                                "Unknown"
                            ),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_AVTransportID",
                            dataType = "i4",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_RcsID",
                            dataType = "i4",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_ConnectionID",
                            dataType = "i4",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_ConnectionManager",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "SourceProtocolInfo",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "SinkProtocolInfo",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_Direction",
                            dataType = "string",
                            allowedValueList = listOf("Input", "Output"),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "CurrentConnectionIDs",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        )
                    )
                ),
                Service(
                    metadata = DeviceManifest.Metadata(
                        location = Url("http://192.0.0.33:50001/cdsSCPD.xml"),
                        urns = emptyMap(),
                        specVersion = SpecVersion(major = 1, minor = 0),
                    ),
                    serviceType = "urn:schemas-upnp-org:service:ContentDirectory:1",
                    serviceId = "urn:upnp-org:serviceId:ContentDirectory",
                    scpdUrl = Url("http://192.0.0.33:50001/cdsSCPD.xml"),
                    controlUrl = Url("http://192.0.0.33:50001/ContentDirectory/control"),
                    eventSubUrl = Url("http://192.0.0.33:50001/ContentDirectory/event"),
                    actionList = listOf(
                        Action(
                            name = "Browse",
                            arguments = listOf(
                                Argument(
                                    name = "ObjectID",
                                    direction = "in",
                                    relatedStateVariable = "A_ARG_TYPE_ObjectID"
                                ),
                                Argument(
                                    name = "BrowseFlag",
                                    direction = "in",
                                    relatedStateVariable = "A_ARG_TYPE_BrowseFlag"
                                ),
                                Argument(name = "Filter", direction = "in", relatedStateVariable = "A_ARG_TYPE_Filter"),
                                Argument(
                                    name = "StartingIndex",
                                    direction = "in",
                                    relatedStateVariable = "A_ARG_TYPE_Index"
                                ),
                                Argument(
                                    name = "RequestedCount",
                                    direction = "in",
                                    relatedStateVariable = "A_ARG_TYPE_Count"
                                ),
                                Argument(
                                    name = "SortCriteria",
                                    direction = "in",
                                    relatedStateVariable = "A_ARG_TYPE_SortCriteria"
                                ),
                                Argument(
                                    name = "Result",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_Result"
                                ),
                                Argument(
                                    name = "NumberReturned",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_Count"
                                ),
                                Argument(
                                    name = "TotalMatches",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_Count"
                                ),
                                Argument(
                                    name = "UpdateID",
                                    direction = "out",
                                    relatedStateVariable = "A_ARG_TYPE_UpdateID"
                                )
                            )
                        ),
                        Action(
                            name = "GetSearchCapabilities",
                            arguments = listOf(
                                Argument(
                                    name = "SearchCaps",
                                    direction = "out",
                                    relatedStateVariable = "SearchCapabilities"
                                )
                            )
                        ),
                        Action(
                            name = "GetSortCapabilities",
                            arguments = listOf(
                                Argument(
                                    name = "SortCaps",
                                    direction = "out",
                                    relatedStateVariable = "SortCapabilities"
                                )
                            )
                        ),
                        Action(
                            name = "GetSystemUpdateID",
                            arguments = listOf(
                                Argument(name = "Id", direction = "out", relatedStateVariable = "SystemUpdateID")
                            )
                        )
                    ),
                    serviceStateTable = listOf(
                        StateVariable(
                            name = "A_ARG_TYPE_BrowseFlag",
                            dataType = "string",
                            allowedValueList = listOf("BrowseMetadata", "BrowseDirectChildren"),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "SystemUpdateID",
                            dataType = "ui4",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_Count",
                            dataType = "ui4",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_SortCriteria",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "SortCapabilities",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_Index",
                            dataType = "ui4",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_ObjectID",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_UpdateID",
                            dataType = "ui4",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_Result",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "SearchCapabilities",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        ),
                        StateVariable(
                            name = "A_ARG_TYPE_Filter",
                            dataType = "string",
                            allowedValueList = emptyList(),
                            sendEvents = false,
                            isMulticast = false
                        )
                    )
                )
            ),
            devices = emptyList()
        )
    )

    @Test
    fun `happy path add flow`() {
        runBlocking<Unit> {
            val deviceDescrXml = this::class.java.getResource("/stubs/flowMediaServerDeviceDescr.xml")?.readBytes()
                ?: throw IllegalStateException("Stub with device description not found")
            val connmgrSCPD = this::class.java.getResource("/stubs/flowMediaServerConnmgrSCPD.xml")?.readBytes()
                ?: throw IllegalStateException("Stub with service description not found")
            val cdsSCPD = this::class.java.getResource("/stubs/flowMediaServerCdsSCPD.xml")?.readBytes()
                ?: throw IllegalStateException("Stub with service description not found")

            val mockEngine = MockEngine { request ->
                when (request.url.toString()){
                    "http://192.0.0.33:50001/desc/device.xml" -> respond(
                        deviceDescrXml, HttpStatusCode.OK, getHeaders(2185)
                    )
                    "http://192.0.0.33:50001/connmgrSCPD.xml" -> respond(
                        connmgrSCPD, HttpStatusCode.OK, getHeaders(4099)
                    )
                    "http://192.0.0.33:50001/cdsSCPD.xml" -> respond(
                        cdsSCPD, HttpStatusCode.OK, getHeaders(4124)
                    )
                    else -> respond(content = "NotFound", status = HttpStatusCode.NotFound)
                }
            }
            DescriptionService.setClientEngine(mockEngine)

            val searchResponse = this::class.java.getResource("/stubs/flowMediaServerSearchResp.txt")?.readBytes()
                ?: throw IllegalStateException("Stub with search response not found")

            val ssdpMsg = SsdpMessage.fromDatagram(DatagramPacket(
                searchResponse,
                searchResponse.size,
                InetAddress.getByName(SSDP_ADDRESS_V4),
                1900
            ))

            val formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz")
                .withZone(ZoneId.of("GMT"))

            ssdpMsg.setHeader(Headers.Date, formatter.format(Instant.ofEpochMilli(System.currentTimeMillis()-200)))
            messagesChannel.send(ssdpMsg)

            notificationService.addNotifications
                .validateNotification()
                .createDeviceManifest()
                .take(1)
                .collect{
                    assertEquals(expectedDeviceManifest, it)
                }

        }
    }
}